package com.fuyun.microservice.proxy.forward;

import com.fuyun.microservice.proxy.ForwardContext;
import com.fuyun.microservice.proxy.RestForward;
import com.netflix.client.ClientException;
import com.netflix.hystrix.exception.HystrixRuntimeException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.netflix.ribbon.support.RibbonRequestCustomizer;
import org.springframework.cloud.netflix.zuul.filters.ProxyRequestHelper;
import org.springframework.cloud.netflix.zuul.filters.route.RibbonCommand;
import org.springframework.cloud.netflix.zuul.filters.route.RibbonCommandContext;
import org.springframework.cloud.netflix.zuul.filters.route.RibbonCommandFactory;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;

import javax.servlet.http.HttpServletRequest;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;


@Slf4j
public class RibbonRestRestForward implements RestForward {
    protected ProxyRequestHelper helper;
    protected RibbonCommandFactory<?> ribbonCommandFactory;
    protected List<RibbonRequestCustomizer> requestCustomizers;
    private boolean useServlet31 = true;

    public RibbonRestRestForward(ProxyRequestHelper helper,
                                 RibbonCommandFactory<?> ribbonCommandFactory,
                                 List<RibbonRequestCustomizer> requestCustomizers) {
        this.helper = helper;
        this.ribbonCommandFactory = ribbonCommandFactory;
        this.requestCustomizers = requestCustomizers;
    }

    @Override
    public ClientHttpResponse forward(String serviceId, ForwardContext forwardContext) {
        RibbonCommandContext commandContext = buildCommandContext(serviceId, forwardContext);
        RibbonCommand command = this.ribbonCommandFactory.create(commandContext);
        try {
            ClientHttpResponse response = command.execute();
            return response;
        } catch (HystrixRuntimeException ex) {
            return handleException(serviceId, ex);
        }
    }


    protected RibbonCommandContext buildCommandContext(String serviceId, ForwardContext forwardContext) {
        MultiValueMap<String, String> headers = buildRequestHeaders();
        MultiValueMap<String, String> params =  new LinkedMultiValueMap<>();
        String verb = "POST";
        InputStream requestEntity = new ByteArrayInputStream(forwardContext.getBodyBytes());

        Boolean retryable = true;
        String loadBalancerKey = forwardContext.getRemoteIp();

        // remove double slashes
        String uri = forwardContext.getUri().replace("//", "/");
//        useServlet31 ? request.getContentLengthLong() : request.getContentLength();
        long contentLength =forwardContext.getBodyLength();
        return new RibbonCommandContext(serviceId, verb, uri, retryable, headers, params,
                requestEntity, this.requestCustomizers, contentLength, loadBalancerKey);
    }

    protected MultiValueMap<String, String> buildRequestHeaders() {
        MultiValueMap<String, String> headers = new HttpHeaders();
        headers.add("Content-Type", "application/json");
        return headers;
    }

    protected String getVerb(HttpServletRequest request) {
        String method = request.getMethod();
        if (method == null) {
            return "GET";
        }
        return method;
    }

    protected InputStream getRequestBody(HttpServletRequest request) {
        InputStream requestEntity = null;
        try {
            requestEntity = request.getInputStream();
            if (requestEntity == null) {
                requestEntity = request.getInputStream();
            }
        } catch (IOException ex) {
            log.error("Error during getRequestBody", ex);
        }
        return requestEntity;
    }

    protected ClientHttpResponse handleException(String serviceId,
                                                 HystrixRuntimeException ex) {
        int statusCode = HttpStatus.INTERNAL_SERVER_ERROR.value();
        Throwable cause = ex;
        String message = ex.getFailureType().toString();

        ClientException clientException = findClientException(ex);
        if (clientException == null) {
            clientException = findClientException(ex.getFallbackException());
        }

        if (clientException != null) {
            if (clientException
                    .getErrorType() == ClientException.ErrorType.SERVER_THROTTLED) {
                statusCode = HttpStatus.SERVICE_UNAVAILABLE.value();
            }
            cause = clientException;
            message = clientException.getErrorType().toString();
        }

        throw new RuntimeException(String.format("Forwarding error,statusCode:%s,message:%s", statusCode, message), cause);
    }

    protected ClientException findClientException(Throwable t) {
        if (t == null) {
            return null;
        }
        if (t instanceof ClientException) {
            return (ClientException) t;
        }
        return findClientException(t.getCause());
    }
}
